druntime: defer rt_term to atexit on Emscripten#5129
Open
ramen-bully wants to merge 1 commit into
Open
Conversation
Emscripten programs that register an async event loop (sokol_app,
emscripten_set_main_loop, emscripten_request_animation_frame_loop,
etc.) return from main while user code is still scheduled to run via
JS callbacks. The unmodified `_d_run_main2` epilogue runs `rt_term`
immediately, which tears down the GC (`gc_term` → `Gcx.Dtor` → unmap
every pool). Subsequent malloc()s from those callbacks reuse the
addresses and clobber any live D objects allocated before main
returned.
Register `rt_term` via `atexit` on Emscripten instead of calling it
inline. This handles all three cases correctly:
- `EXIT_RUNTIME=0` async programs (browser tabs): atexit never
fires, the page lives until the tab closes, the GC stays alive
while rAF callbacks run.
- `EXIT_RUNTIME=1` sync programs: emscripten runs atexit handlers
when main returns, rt_term fires at the right time, module
destructors and `gc_term` run normally.
- `EXIT_RUNTIME=1` programs that call `exit()` after cancelling
their event loop: same as above — atexit fires on `exit()`.
The deferral preserves D-side teardown semantics for programs that
genuinely want them, while preventing the heap-clobber for programs
whose "main returned" is only a JS-scheduling artifact.
Gated on `version (Emscripten)`; other targets unchanged.
kinke
reviewed
May 14, 2026
| { | ||
| rt_term(); | ||
| } | ||
| } |
Member
There was a problem hiding this comment.
I suggest putting this as nested static function directly above the single usage below.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
DISCLAMER: i used AI for finding and writing this, i don't know anything about compilers or emscripten..
sorry if this is completely wrong, i tested with some sokol projects and it fixed my project.
Emscripten programs that register an async event loop (sokol_app, emscripten_set_main_loop, emscripten_request_animation_frame_loop, etc.) return from main while user code is still scheduled to run via JS callbacks. The unmodified
_d_run_main2epilogue runsrt_termimmediately, which tears down the GC (gc_term→Gcx.Dtor→ unmap every pool). Subsequent malloc()s from those callbacks reuse the addresses and clobber any live D objects allocated before main returned.Register
rt_termviaatexiton Emscripten instead of calling it inline. This handles all three cases correctly:EXIT_RUNTIME=0async programs (browser tabs): atexit never fires, the page lives until the tab closes, the GC stays alive while rAF callbacks run.EXIT_RUNTIME=1sync programs: emscripten runs atexit handlers when main returns, rt_term fires at the right time, module destructors andgc_termrun normally.EXIT_RUNTIME=1programs that callexit()after cancelling their event loop: same as above — atexit fires onexit().The deferral preserves D-side teardown semantics for programs that genuinely want them, while preventing the heap-clobber for programs whose "main returned" is only a JS-scheduling artifact.
Gated on
version (Emscripten); other targets unchanged.